// Copyright (c) ZeroC, Inc.

import Foundation
import IceImpl

private class ConnectionInfoI: ConnectionInfo {
    var underlying: ConnectionInfo?
    var incoming: Bool
    var adapterName: String
    var connectionId: String

    init(underlying: ConnectionInfo?, incoming: Bool, adapterName: String, connectionId: String) {
        self.underlying = underlying
        self.incoming = incoming
        self.adapterName = adapterName
        self.connectionId = connectionId
    }
}

private class IPConnectionInfoI: ConnectionInfoI, IPConnectionInfo {
    var localAddress: String
    var localPort: Int32
    var remoteAddress: String
    var remotePort: Int32

    init(
        underlying: ConnectionInfo?, incoming: Bool, adapterName: String, connectionId: String,
        localAddress: String, localPort: Int32, remoteAddress: String, remotePort: Int32
    ) {
        self.localAddress = localAddress
        self.localPort = localPort
        self.remoteAddress = remoteAddress
        self.remotePort = remotePort
        super.init(
            underlying: underlying, incoming: incoming, adapterName: adapterName,
            connectionId: connectionId)
    }
}

private class TCPConnectionInfoI: IPConnectionInfoI, TCPConnectionInfo {
    var rcvSize: Int32
    var sndSize: Int32

    init(
        underlying: ConnectionInfo?, incoming: Bool, adapterName: String, connectionId: String,
        localAddress: String, localPort: Int32, remoteAddress: String, remotePort: Int32,
        rcvSize: Int32, sndSize: Int32
    ) {
        self.rcvSize = rcvSize
        self.sndSize = sndSize
        super.init(
            underlying: underlying, incoming: incoming, adapterName: adapterName,
            connectionId: connectionId,
            localAddress: localAddress, localPort: localPort,
            remoteAddress: remoteAddress, remotePort: remotePort)
    }
}

private class UDPConnectionInfoI: IPConnectionInfoI, UDPConnectionInfo {
    var mcastAddress: String
    var mcastPort: Int32
    var rcvSize: Int32
    var sndSize: Int32

    init(
        underlying: ConnectionInfo?, incoming: Bool, adapterName: String, connectionId: String,
        localAddress: String, localPort: Int32, remoteAddress: String, remotePort: Int32,
        mcastAddress: String, mcastPort: Int32, rcvSize: Int32, sndSize: Int32
    ) {
        self.mcastAddress = mcastAddress
        self.mcastPort = mcastPort
        self.rcvSize = rcvSize
        self.sndSize = sndSize
        super.init(
            underlying: underlying, incoming: incoming, adapterName: adapterName,
            connectionId: connectionId,
            localAddress: localAddress, localPort: localPort,
            remoteAddress: remoteAddress, remotePort: remotePort)
    }
}

private class WSConnectionInfoI: ConnectionInfoI, WSConnectionInfo {
    var headers: HeaderDict

    init(
        underlying: ConnectionInfo?, incoming: Bool, adapterName: String, connectionId: String,
        headers: HeaderDict
    ) {
        self.headers = headers
        super.init(
            underlying: underlying, incoming: incoming, adapterName: adapterName,
            connectionId: connectionId)
    }
}

private class SSLConnectionInfoI: ConnectionInfoI, SSLConnectionInfo {
    var peerCertificate: SecCertificate?

    init(
        underlying: ConnectionInfo?, incoming: Bool, adapterName: String, connectionId: String,
        peerCertificate: String
    ) {
        let beginPrefix = "-----BEGIN CERTIFICATE-----\n"
        let endPrefix = "\n-----END CERTIFICATE-----\n"

        var raw = peerCertificate
        if raw.hasPrefix(beginPrefix) {
            raw = String(raw.dropFirst(beginPrefix.count))
            raw = String(raw.dropLast(endPrefix.count))
        }

        self.peerCertificate = nil
        if let data = NSData(base64Encoded: raw, options: .ignoreUnknownCharacters) {
            if let c = SecCertificateCreateWithData(kCFAllocatorDefault, data) {
                self.peerCertificate = c
            }
        }

        super.init(
            underlying: underlying, incoming: incoming, adapterName: adapterName,
            connectionId: connectionId)
    }
}

#if os(iOS) || os(watchOS) || os(tvOS)
    private class IAPConnectionInfoI: ConnectionInfoI, IAPConnectionInfo {
        var name: String
        var manufacturer: String
        var modelNumber: String
        var firmwareRevision: String
        var hardwareRevision: String
        var `protocol`: String

        init(
            underlying: ConnectionInfo?, incoming: Bool, adapterName: String, connectionId: String,
            name: String, manufacturer: String, modelNumber: String, firmwareRevision: String,
            hardwareRevision: String,
            protocol: String
        ) {
            self.name = name
            self.manufacturer = manufacturer
            self.modelNumber = modelNumber
            self.firmwareRevision = firmwareRevision
            self.hardwareRevision = hardwareRevision
            self.protocol = `protocol`
            super.init(
                underlying: underlying, incoming: incoming, adapterName: adapterName,
                connectionId: connectionId)
        }
    }
#endif

class ConnectionInfoFactory: ICEConnectionInfoFactory {
    static func createIPConnectionInfo(
        _ underlying: Any,
        incoming: Bool,
        adapterName: String,
        connectionId: String,
        localAddress: String,
        localPort: Int32,
        remoteAddress: String,
        remotePort: Int32
    ) -> Any {
        return IPConnectionInfoI(
            underlying: getUnderlying(underlying),
            incoming: incoming,
            adapterName: adapterName,
            connectionId: connectionId,
            localAddress: localAddress,
            localPort: localPort,
            remoteAddress: remoteAddress,
            remotePort: remotePort)
    }

    static func createTCPConnectionInfo(
        _ underlying: Any,
        incoming: Bool,
        adapterName: String,
        connectionId: String,
        localAddress: String,
        localPort: Int32,
        remoteAddress: String,
        remotePort: Int32,
        rcvSize: Int32,
        sndSize: Int32
    ) -> Any {
        return TCPConnectionInfoI(
            underlying: getUnderlying(underlying), incoming: incoming,
            adapterName: adapterName,
            connectionId: connectionId,
            localAddress: localAddress,
            localPort: localPort,
            remoteAddress: remoteAddress,
            remotePort: remotePort,
            rcvSize: rcvSize,
            sndSize: sndSize)
    }

    static func createUDPConnectionInfo(
        _ underlying: Any,
        incoming: Bool,
        adapterName: String,
        connectionId: String,
        localAddress: String,
        localPort: Int32,
        remoteAddress: String,
        remotePort: Int32, mcastAddress: String, mcastPort: Int32,
        rcvSize: Int32,
        sndSize: Int32
    ) -> Any {
        return UDPConnectionInfoI(
            underlying: getUnderlying(underlying), incoming: incoming,
            adapterName: adapterName,
            connectionId: connectionId,
            localAddress: localAddress,
            localPort: localPort,
            remoteAddress: remoteAddress,
            remotePort: remotePort,
            mcastAddress: mcastAddress,
            mcastPort: mcastPort,
            rcvSize: rcvSize,
            sndSize: sndSize)
    }

    static func createWSConnectionInfo(
        _ underlying: Any,
        incoming: Bool,
        adapterName: String,
        connectionId: String,
        headers: [String: String]
    ) -> Any {
        return WSConnectionInfoI(
            underlying: getUnderlying(underlying),
            incoming: incoming,
            adapterName: adapterName,
            connectionId: connectionId,
            headers: headers)
    }

    static func createSSLConnectionInfo(
        _ underlying: Any,
        incoming: Bool,
        adapterName: String,
        connectionId: String,
        peerCertificate: String
    ) -> Any {
        return SSLConnectionInfoI(
            underlying: getUnderlying(underlying),
            incoming: incoming,
            adapterName: adapterName,
            connectionId: connectionId,
            peerCertificate: peerCertificate)
    }

    #if os(iOS) || os(watchOS) || os(tvOS)

        static func createIAPConnectionInfo(
            _ underlying: Any,
            incoming: Bool,
            adapterName: String,
            connectionId: String,
            name: String,
            manufacturer: String,
            modelNumber: String,
            firmwareRevision: String,
            hardwareRevision: String,
            protocol: String
        ) -> Any {
            return IAPConnectionInfoI(
                underlying: getUnderlying(underlying),
                incoming: incoming,
                adapterName: adapterName,
                connectionId: connectionId,
                name: name,
                manufacturer: manufacturer,
                modelNumber: modelNumber,
                firmwareRevision: firmwareRevision,
                hardwareRevision: hardwareRevision,
                protocol: `protocol`)
        }

    #endif

    static func getUnderlying(_ info: Any) -> ConnectionInfo? {
        return info as? ConnectionInfo
    }
}
